package rest

import (
	"context"
	"encoding/json"
	"net/http"
	v1 "wxpay/pkg/api/v1"
	"wxpay/pkg/mcontext"
	"wxpay/pkg/merrors"

	"google.golang.org/grpc/status"

	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

func customHeaderMatcher(key string) (string, bool) {
	switch key {
	case "Token":
		return key, true
	default:
		return runtime.DefaultHeaderMatcher(key)
	}
}

type errorBody struct {
	Err string `json:"error_code,omitempty"`
	Msg string `json:"error_message,omitempty"`
}

func customProtoErrorHandler(_ context.Context, _ *runtime.ServeMux, m runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
	const fallback = `{"error_code": "Unknown", "error_message": "failed to marshal error message"}`

	w.Header().Set("Content-type", m.ContentType())
	w.WriteHeader(http.StatusOK)
	s := status.Convert(err)
	eBody := errorBody{}
	if e := merrors.GetGrpcErrorInfo(err); e != nil {
		// 尝试获取details中的ErrorInfo
		eBody.Err = e.Metadata["ErrorCode"]
		eBody.Msg = e.Metadata["ErrorMessage"]
	} else {
		eBody.Err = s.Code().String()
		eBody.Msg = s.Message()
	}

	jErr := json.NewEncoder(w).Encode(eBody)

	if jErr != nil {
		_, _ = w.Write([]byte(fallback))
	}
}

func preflightHandler(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
	w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE")
}

// allowCORS allows Cross Origin Resource Sharing from any origin.
// Don't do this without consideration in production systems.
func allowCORS(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		preflightHandler(w, r)
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func RunServer(ctx context.Context, grpcPort, httpPort string) error {
	logger := mcontext.Logger(ctx)
	opts := []grpc.DialOption{grpc.WithInsecure()}

	// Register grpc-gateway
	jsonOpt := runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true})
	headerOpt := runtime.WithIncomingHeaderMatcher(customHeaderMatcher)
	errorOpt := runtime.WithProtoErrorHandler(customProtoErrorHandler)
	serveMux := runtime.NewServeMux(jsonOpt, headerOpt, errorOpt)

	err := v1.RegisterWxpayHandlerFromEndpoint(ctx, serveMux, "localhost"+grpcPort, opts)
	if err != nil {
		logger.Fatal("register client error: ", zap.Error(err))
	}
	logger.Info("REST server ready...")
	return http.ListenAndServe(httpPort, allowCORS(serveMux))
}
